package info.rflow.model;

import info.rflow.Utils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;


/**
 * Document me!
 *
 * @author Holger Brandl
 */
public class RWorkflowModel {

    List<RBlock> codeBlocks = new ArrayList<RBlock>();
    private File rFile;

    private boolean hasChanged = false;
    private File workingDirectory;

    private WorkflowOutputCache outputCache;
    private long rFileModTime;


    public RWorkflowModel(File rFile) {
        setRFile(rFile);
    }


    public File getRFile() {
        return rFile;
    }


    public void setRFile(File rFile) {
        this.rFile = rFile;

        if (rFile != null)
            this.rFileModTime = this.rFile.lastModified();
    }


    public void addBlock(RBlock rBlock) {
        codeBlocks.add(rBlock);
    }


    public List<RBlock> getBlocks() {
        return codeBlocks;
    }


    public RBlock getBlockByOutput(String inputName) {
        for (RBlock codeBlock : codeBlocks) {
            if (codeBlock.getOutputs().contains(inputName)) {
                return codeBlock;
            }
        }

        return null;
    }


    public String getScript() {
        StringBuilder sb = new StringBuilder();

        for (RBlock codeBlock : codeBlocks) {
            sb.append(codeBlock.getBlockCode());
        }

        return sb.toString();
    }


    public void save(File saveFile) {
        try {
            rFile = saveFile;

            BufferedWriter bf = new BufferedWriter(new FileWriter(saveFile));
            bf.write(getScript());
            bf.flush();
            bf.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    public int getLineNumber(RBlock rBlock) {
        int lineCounter = 0;
        for (RBlock codeBlock : codeBlocks) {
            if (codeBlock.equals(rBlock)) {
                return lineCounter + 1;
            }

            lineCounter += codeBlock.getCode().split(System.getProperty("line.separator")).length;
        }

        // better error handling??
        return 1;
    }


    public void execute(Collection<RBlock> execBlocks) {
        if (workingDirectory == null) {
            Utils.warnMissingWD();
            return;
        }

        // collect all blocks that need to be executed
        HashSet<RBlock> deps = new HashSet<RBlock>();
//        deps.addAll(execBlocks);

        for (RBlock execBlock : execBlocks) {
            deps.addAll(execBlock.getDependencies());
        }

        // filter away the already executed nodes
        HashSet<RBlock> deps2Execute = new HashSet<RBlock>();
        for (RBlock dependencyBlock : deps) {
            if (!dependencyBlock.isExecuted()) {
                deps2Execute.add(dependencyBlock);
            }
        }

        // add the blocks the users flagged for execution
        deps2Execute.addAll(execBlocks);


        // order blocks by execution order
        ArrayList<RBlock> execSchedule = new ArrayList<RBlock>(deps2Execute);
        Collections.sort(execSchedule, new Comparator<RBlock>() {
            @Override
            public int compare(RBlock rBlock1, RBlock rBlock2) {
                return rBlock1.getDependencies().contains(rBlock2) ? 1 : -1;
            }
        });


        // make sure that we have a connection to R
        ROutputListener.initialize();
        new BlockExecutionScheduler(execSchedule).start();
    }


    public RBlock getOuputNode(String outputFileName) {
        for (RBlock codeBlock : codeBlocks) {
            if (codeBlock.getOutputs().contains(outputFileName))
                return codeBlock;
        }

        // todo make sure that the output-name is just used once --> throw error otherwise
        return null;

    }


    public Collection<RBlock> getInputNodes(String inputName) {
        Collection<RBlock> inputs = new ArrayList<RBlock>();

        for (RBlock codeBlock : codeBlocks) {
            if (codeBlock.getInputs().contains(inputName))
                inputs.add(codeBlock);
        }

        return inputs;
    }


    public void setChanged(boolean hasChanged) {
        this.hasChanged = hasChanged;
    }


    public void setWorkingDirectory(File scriptWD) {
        this.workingDirectory = scriptWD;

        this.outputCache = WorkflowOutputCache.getCache(workingDirectory);
    }


    public File getWorkingDirectory() {
        return workingDirectory;
    }


    public WorkflowOutputCache getOutputCache() {
        return outputCache;
    }


    public long getRFileModTime() {
        return rFileModTime;
    }
}
